﻿using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using MyFramework.Common.Interfaces;
using MyFramework.Helpers;

namespace MyFramework.Shell
{
    /// <summary>
    /// Exception descriptor
    /// </summary>
    public class ExceptionDescriptor : IExceptionDescriptor
    {
        /// <summary>
        /// Translate exception object to string, with additional system info
        /// </summary>
        /// <param name="exception"></param>
        /// <returns></returns>
        public virtual string GetExceptionDescription(Exception exception)
        {
            StringBuilder sb = new StringBuilder();

            if ((exception.InnerException != null))
            {
                sb.Append("(Inner Exception)");
                sb.Append(Environment.NewLine);
                sb.Append(GetExceptionDescription(exception.InnerException));
                sb.Append(Environment.NewLine);
                sb.Append("(Outer Exception)");
                sb.Append(Environment.NewLine);
            }

            //get general system and app information
            sb.Append(SysInfoToString(true));

            //get exception-specific information
            sb.Append("Exception Source:      ");
            try
            {
                sb.Append(exception.Source);
            }
            catch (Exception e)
            {
                sb.Append(e.Message);
            }

            sb.Append(Environment.NewLine);
            sb.Append("Exception Type:        ");
            try
            {
                sb.Append(exception.GetType().FullName);
            }
            catch (Exception e)
            {
                sb.Append(e.Message);
            }

            sb.Append(Environment.NewLine);
            sb.Append("Exception Message:     ");
            try
            {
                sb.Append(exception.Message);
            }
            catch (Exception e)
            {
                sb.Append(e.Message);
            }

            sb.Append(Environment.NewLine);
            sb.Append("Exception Target Site: ");
            try
            {
                sb.Append(exception.TargetSite.Name);
            }
            catch (Exception e)
            {
                sb.Append(e.Message);
            }
            sb.Append(Environment.NewLine);

            try
            {
                sb.Append(GetEnhancedStackTrace(exception));
            }
            catch (Exception e)
            {
                sb.Append(e.Message);
            }

            sb.Append(Environment.NewLine);

            return sb.ToString();
        }

        /// <summary>
        /// Gathers some system information that is helpful to diagnose the exception.
        /// </summary>
        /// <param name="includeStackTrace">if set to <c>true</c> [include stack trace].</param>
        /// <returns></returns>
        protected internal string SysInfoToString(bool includeStackTrace)
        {
            StringBuilder sb = new StringBuilder();

            sb.Append("Date and Time:         ");
            sb.Append(DateTime.Now);
            sb.Append(Environment.NewLine);

            sb.Append("Machine Name:          ");
            try
            {
                sb.Append(Environment.MachineName);
            }
            catch (Exception e)
            {
                sb.Append(e.Message);
            }
            sb.Append(Environment.NewLine);

            sb.Append("IP Address:            ");
            sb.Append(ExceptionHelper.GetCurrentIP());
            sb.Append(Environment.NewLine);

            sb.Append("Current User:          ");
            sb.Append(ExceptionHelper.GetUserIdentity());
            sb.Append(Environment.NewLine);
            sb.Append(Environment.NewLine);

            sb.Append("Application Domain:    ");
            try
            {
                sb.Append(System.AppDomain.CurrentDomain.FriendlyName);
            }
            catch (Exception e)
            {
                sb.Append(e.Message);
            }


            sb.Append(Environment.NewLine);
            sb.Append("Assembly Codebase:     ");
            try
            {
                sb.Append(ExceptionHelper.GetParentAssembly().CodeBase);
            }
            catch (Exception e)
            {
                sb.Append(e.Message);
            }
            sb.Append(Environment.NewLine);

            sb.Append("Assembly Full Name:    ");
            try
            {
                sb.Append(ExceptionHelper.GetParentAssembly().FullName);
            }
            catch (Exception e)
            {
                sb.Append(e.Message);
            }
            sb.Append(Environment.NewLine);

            sb.Append("Assembly Version:      ");
            try
            {
                sb.Append(ExceptionHelper.GetParentAssembly().GetName().Version.ToString());
            }
            catch (Exception e)
            {
                sb.Append(e.Message);
            }
            sb.Append(Environment.NewLine);

            sb.Append("Assembly Build Date:   ");
            try
            {
                sb.Append(File.GetLastWriteTime(ExceptionHelper.GetParentAssembly().Location).ToString());
            }
            catch (Exception e)
            {
                sb.Append(e.Message);
            }

            sb.Append(Environment.NewLine);
            sb.Append(Environment.NewLine);

            //if (includeStackTrace)
            //{
            //    sb.Append(GetEnhancedStackTrace());
            //}

            return sb.ToString();
        }

        /// <summary>
        /// Turns a single stack frame object into an informative string.
        /// </summary>
        /// <param name="stackFrame"></param>
        /// <returns></returns>
        public string StackFrameToString(StackFrame stackFrame)
        {
            StringBuilder sb = new StringBuilder();
            int numParams = 0;
            MemberInfo mi = stackFrame.GetMethod();

            //build method name
            sb.Append("   ");
            sb.Append(mi.DeclaringType.Namespace);
            sb.Append(".");
            sb.Append(mi.DeclaringType.Name);
            sb.Append(".");
            sb.Append(mi.Name);

            //build method params
            ParameterInfo[] parameters = stackFrame.GetMethod().GetParameters();
            ParameterInfo parameter = null;
            sb.Append("(");
            numParams = 0;
            foreach (ParameterInfo parameterInfo in parameters)
            {
                parameter = parameterInfo;
                numParams += 1;
                if (numParams > 1)
                {
                    sb.Append(", ");
                }

                sb.Append(parameter.ParameterType.Name + " ");
                sb.Append(parameter.Name);
            }

            sb.Append(")");
            sb.Append(Environment.NewLine);

            //if source code is available, append location info
            sb.Append("       ");
            if (stackFrame.GetFileName() == null || stackFrame.GetFileName().Length == 0)
            {
                sb.Append(System.IO.Path.GetFileName(ExceptionHelper.GetParentAssembly().CodeBase));
                //native code offset is always available
                sb.Append(": N ");
                sb.Append(string.Format("{0:#00000}", stackFrame.GetNativeOffset()));

            }
            else
            {
                sb.Append(System.IO.Path.GetFileName(stackFrame.GetFileName()));
                sb.Append(": line ");
                sb.Append(stackFrame.GetFileLineNumber());
                sb.Append(", column ");
                sb.Append(stackFrame.GetFileColumnNumber());
                //if IL is available, append IL location info
                if (stackFrame.GetILOffset() != StackFrame.OFFSET_UNKNOWN)
                {
                    sb.Append(", IL ");
                    sb.Append(string.Format("{0:#0000}", stackFrame.GetILOffset()));
                }
            }

            sb.Append(Environment.NewLine);
            return sb.ToString();
        }

        /// <summary>
        /// Gets the enhanced stack trace.
        /// </summary>
        /// <param name="stackTrace">The stack trace.</param>
        /// <returns></returns>
        protected string GetEnhancedStackTrace(StackTrace stackTrace)
        {
            return GetEnhancedStackTrace(stackTrace, string.Empty);
        }

        /// <summary>
        /// Gets the enhanced stack trace generator.
        /// </summary>
        /// <param name="stackTrace">The obj stack trace.</param>
        /// <param name="skipClassName">Name of the skip class.</param>
        /// <returns></returns>
        protected string GetEnhancedStackTrace(StackTrace stackTrace, string skipClassName)
        {
            StringBuilder sb = new StringBuilder();

            sb.Append(Environment.NewLine);
            sb.Append("---- Stack Trace ----");
            sb.Append(Environment.NewLine);

            for (int intFrame = 0; intFrame <= stackTrace.FrameCount - 1; intFrame++)
            {
                StackFrame sf = stackTrace.GetFrame(intFrame);
                MemberInfo mi = sf.GetMethod();

                if (string.IsNullOrEmpty(skipClassName) || mi.DeclaringType.Name.IndexOf(skipClassName) <= -1)
                {
                    sb.Append(StackFrameToString(sf));
                }
            }

            sb.Append(Environment.NewLine);

            return sb.ToString();
        }

        /// <summary>
        /// Gets the enhanced stack trace generator.
        /// </summary>
        /// <param name="exception">The exception.</param>
        /// <returns></returns>
        protected string GetEnhancedStackTrace(Exception exception)
        {
            StackTrace stackTrace = new StackTrace(exception, true);
            return GetEnhancedStackTrace(stackTrace);
        }
    }
}
